﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Brin.Concurrent
{
    /// <summary>
    /// Provides atomic lock-free, non blocking operations for variables that are shared by multiple threads.
    /// </summary>
    /// <remarks> The computation section in each function is not generalized because of performance reasons</remarks>
    public static class InterlockedEx
    {
        /// <summary>
        /// Returns the product of 2 32-bit integers, and replaces the first number with the product, as an atomic operation
        /// </summary>
        /// <remarks>
        /// This method handles an overflow condition by wrapping:
        /// if the value at destination is int.MaxValue and value is 1, the result is int.MinValue and so on. 
        /// No exception is thrown.
        /// </remarks>
        /// <param name="destination">A variable containing the first value to be multiplied. The product of the two values is stored in destination</param>
        /// <param name="b">The value to be multiplied by destination</param>
        /// <returns>The product value stored at destination</returns>
        public static int Mul(ref int destination, int b)
        {
            //holds the computation result
            int result;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = latestRead * b;
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the product of 2 64-bit integers, and replaces the first number with the product, as an atomic operation
        /// </summary>
        /// <remarks>
        /// This method handles an overflow condition by wrapping:
        /// if the value at destination is long.MaxValue and value is 1, the result is long.MinValue and so on. 
        /// No exception is thrown.
        /// </remarks>
        /// <param name="destination">A variable containing the first value to be multiplied. The product of the two values is stored in destination</param>
        /// <param name="b">The value to be multiplied by destination</param>
        /// <returns>The product value stored at destination</returns>
        public static long Mul(ref long destination, long b)
        {
            //holds the computation result
            long result;

            //a local variable containing the latest value of 'destination'
            long latestRead = Interlocked.Read(ref destination);

            //initial iteration value
            long initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = latestRead * b;
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the smaller of two 32-bit integers, and replaces the first number with the smaller value, as an atomic operation
        /// </summary>
        /// <param name="destination">A variable containing the first value to compare with b. The minimum of the two values is stored in destination</param>
        /// <param name="b">The value to compare with destination</param>
        /// <returns>The minimum value stored at destination</returns>
        public static int Min(ref int destination, int b)
        {
            //holds the computation result
            int result;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = Math.Min(latestRead, b);
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the smaller of two 64-bit integers, and replaces the first number with the smaller value, as an atomic operation
        /// </summary>
        /// <param name="destination">A variable containing the first value to compare with b. The minimum of the two values is stored in destination</param>
        /// <param name="b">The value to compare with destination</param>
        /// <returns>The minimum value stored at destination</returns>
        public static long Min(ref long destination, long b)
        {
            //holds the computation result
            long result;

            //a local variable containing the latest value of 'destination'
            long latestRead = Interlocked.Read(ref destination);

            //initial iteration value
            long initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = Math.Min(latestRead, b);
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the larger of two 32-bit integers, and replaces the first number with the larger value, as an atomic operation
        /// </summary>
        /// <param name="destination">A variable containing the first value to compare with b. The maximum of the two values is stored in destination</param>
        /// <param name="b">The value to compare with destination</param>
        /// <returns>The maximum value stored at destination</returns>
        public static int Max(ref int destination, int b)
        {
            //holds the computation result
            int result;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = Math.Max(latestRead, b);
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the larger of two 64-bit integers, and replaces the first number with the larger value, as an atomic operation
        /// </summary>
        /// <param name="destination">A variable containing the first value to compare with b. The maximum of the two values is stored in destination</param>
        /// <param name="b">The value to compare with destination</param>
        /// <returns>The maximum value stored at destination</returns>
        public static long Max(ref long destination, long b)
        {
            //holds the computation result
            long result;

            //a local variable containing the latest value of 'destination'
            long latestRead = Interlocked.Read(ref destination);

            //initial iteration value
            long initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = Math.Max(latestRead, b);
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the bitwise AND of destination and value and stores the result in destination, as an atomic operation
        /// </summary>
        /// <param name="destination">A variable containing the operand which is Anded with the second value, the and result is stored in destination</param>
        /// <param name="value">The second operand</param>
        /// <returns>The bitwise AND result stored in destination</returns>
        public static int And(ref int destination, int value)
        {
            //holds the computation result
            int result;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = latestRead & value;
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the bitwise OR of destination and value and stores the result in destination, as an atomic operation
        /// </summary>
        /// <param name="destination">A variable containing the operand which is Ored with the second value, the and result is stored in destination</param>
        /// <param name="value">The second operand</param>
        /// <returns>The bitwise OR result stored in destination</returns>
        public static int Or(ref int destination, int value)
        {
            //holds the computation result
            int result;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = latestRead | value;
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Returns the bitwise XOR of destination and value and stores the result in destination, as an atomic operation
        /// </summary>
        /// <param name="destination">A variable containing the operand which is Xored with the second value, the and result is stored in destination</param>
        /// <param name="value">The second operand</param>
        /// <returns>The bitwise XOR result stored in destination</returns>
        public static int Xor(ref int destination, int value)
        {
            //holds the computation result
            int result;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //do the computation
                result = latestRead ^ value;
                //do a cas operation, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }

        /// <summary>
        /// Examines bit position in destination and returns its current value
        /// The bit is set to 1 regardless of its current value
        /// </summary>
        /// <param name="destination">A variable containing the value to examine and set</param>
        /// <param name="position">The bit position to set to 1</param>
        /// <returns>The bit's value before the bit's assignment, the result is either 1 or 0</returns>
        public static byte TestBitAndSet(ref int destination, int position)
        {
            //holds the computation result
            int testResult;

            //stores the destination value with position bit set to 1 
            int newValue;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //check if the bit is set to 1
                testResult = (1 << position) & latestRead;
                //set the bit to 1
                newValue = (1 << position) | latestRead;
                //set newValue only if the a cas operation succeeds, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, newValue, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return (byte)(testResult != 0 ? 1 : 0);
        }

        /// <summary>
        /// Examines bit position in destination and returns its current value
        /// The bit is set to 0 regardless of its current value
        /// </summary>
        /// <param name="destination">A variable containing the value to examine and set</param>
        /// <param name="position">The bit position to set to 0</param>
        /// <returns>The bit's value before the bit's assignment, the result either is 1 or 0</returns>
        public static byte TestBitAndReset(ref int destination, int position)
        {
            //holds the computation result
            int testResult;

            //stores the destination value with position bit set to 1 
            int newValue;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //check if the bit is set to 1
                testResult = (1 << position) & latestRead;
                //set the bit to 0
                newValue = (~(1 << position)) & latestRead;
                //set newValue only if the a cas operation succeeds, if it fails then try again
                latestRead = Interlocked.CompareExchange(ref destination, newValue, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return (byte)(testResult != 0 ? 1 : 0);
        }

        /// <summary>
        /// Checks if a 32-bit variable meets a specified criteria and possibly replaces it with a new value based on a specified computation 
        /// </summary>
        /// <param name="destination">A variable containing the value to examine and conditionally set</param>
        /// <param name="criteria">A condition which examines destination's value and determines whether or not destination is set</param>
        /// <param name="computation">A method which possibly modifies destination</param>
        /// <returns>If the criteria is met the new computed value is returned otherwise destination is returned</returns>
        /// <remarks>This method generalizes atomic testing and setting a 32-bit variable by accepting external criteria and computation methods as parameters.
        /// The external methods should be fast and not time consuming. 
        /// The longer time it takes to executes them the more likely other threads
        /// will alter the shared variable (destination) by the time they finish causing the methods to rerun and eventually causing the method to be ineffective and slow 
        /// </remarks>
        public static int ComputeAndSet(ref int destination,Predicate<int> criteria,Func<int,int> computation)
        {
            //holds the computation result
            int result;

            //a local variable containing the latest value of 'destination'
            int latestRead = destination;

            //initial iteration value
            int initial;
            do
            {
                initial = latestRead;
                //the criteria is checked and if it is met then the computation is done
                //otherwise the same value is returned
                result = criteria(latestRead) ? computation(latestRead) : latestRead;
                //Either the new value (computed) or the old value is set based on the cas operation's success 
                //If the cas operation fails the whole cycle of testing and computing is started again because destination has already changed 
                latestRead = Interlocked.CompareExchange(ref destination, result, initial);
            } while (initial != latestRead);

            //return the assigned result because 'destination' might be different 
            //by the time the function returns
            return result;
        }
    }
}
